package net.qiqbframework.springboot.autoconfig;


import net.qiqbframework.config.Configuration;
import net.qiqbframework.config.Configurer;
import net.qiqbframework.config.ConfigurerModule;
import net.qiqbframework.config.ModuleConfiguration;
import net.qiqbframework.domains.event.infra.persistence.DomainEventPersistence;
import net.qiqbframework.eventsourcing.EventStorageEngine;
import net.qiqbframework.eventsourcing.JdbcEventStorageEngine;
import net.qiqbframework.lifecycle.Lifecycle;
import net.qiqbframework.messaging.annotation.HandlerParameterResolverFactory;
import net.qiqbframework.spring.aggregate.SpringAggregateLookup;
import net.qiqbframework.spring.command.SpringCommandLookup;
import net.qiqbframework.spring.config.MessageHandlerLookup;
import net.qiqbframework.spring.config.SpringConfigurer;
import net.qiqbframework.spring.config.SpringParameterResolverFactoryBean;
import net.qiqbframework.spring.config.SpringQiqbConfiguration;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Role;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;


@AutoConfiguration
@ConditionalOnClass(SpringConfigurer.class)

@AutoConfigureAfter({
        QiqbAutoConfiguration.class,
        TransactionAutoConfiguration.class,
        MybatisPlusModuleAutoConfiguration.class,
        MybatisFlexModuleAutoConfiguration.class
})
public class InfraConfiguration {

    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    @Bean
    public static MessageHandlerLookup messageHandlerLookup() {
        return new MessageHandlerLookup();
    }

    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    @Bean
    public static SpringAggregateLookup springAggregateLookup() {
        return new SpringAggregateLookup();
    }

    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    @Bean
    public static SpringCommandLookup springCommandLookup() {
        return new SpringCommandLookup();
    }

    @Bean
    @ConditionalOnMissingBean
    public SpringQiqbConfiguration springQiqbConfiguration(Configurer configurer) {
        return new SpringQiqbConfiguration(configurer);
    }

    @Bean
    @ConditionalOnMissingBean
    public SpringConfigurer springQiqbConfigurer(ConfigurableListableBeanFactory beanFactory,
                                                 List<ConfigurerModule> configurerModules,
                                                 List<ModuleConfiguration> moduleConfigurations) {
        SpringConfigurer configurer = new SpringConfigurer(beanFactory);
        moduleConfigurations.forEach(configurer::registerModule);

        List<ConfigurerModule> sortedList = new ArrayList<>(configurerModules);
        sortedList.sort(Comparator.comparing(ConfigurerModule::order)
                .thenComparing(AnnotationAwareOrderComparator.INSTANCE));
        sortedList.forEach(c -> c.configureModule(configurer));
        return configurer;
    }

    @Bean
    public InitializingBean lifecycleInitializer(Configurer configurer, List<Lifecycle> lifecycleBeans) {
        return () -> configurer.onInitialize(
                config -> lifecycleBeans.forEach(bean -> bean.registerLifecycleHandler(config.lifecycleRegistry()))
        );
    }


    @Primary
    @Bean
    public SpringParameterResolverFactoryBean parameterResolverFactory(
            List<HandlerParameterResolverFactory> parameterResolverFactories
    ) {
        SpringParameterResolverFactoryBean springParameterResolverFactoryBean = new SpringParameterResolverFactoryBean();
        springParameterResolverFactoryBean.setAdditionalFactories(parameterResolverFactories);
        return springParameterResolverFactoryBean;
    }


    @ConditionalOnBean(DomainEventPersistence.class)
    @Bean
    public EventStorageEngine eventStorageEngine(DomainEventPersistence domainEventPersistence, Configuration configuration) {
        return JdbcEventStorageEngine.builder()
                .eventSerializer(configuration.messageSerializer())
                // eventDistributor
                .eventDistributor(configuration.eventDistributor())
                .domainEventPersistence(domainEventPersistence)
                .build();
    }

}
